package blogger

import (
	"bytes"
	"context"
	"fmt"
	"gitee.com/go-boot/boot-interfaces/ilogger"
	"os"
	"runtime"
	"strings"
	"sync"
	"time"
)

var (
	logPackage         string
	minimumCallerDepth int = 1
	callerInitOnce     sync.Once
)

const (
	maximumCallerDepth int = 25
	knownLogFrames     int = 4
)

type LoggerEntry struct {
	Logger  ilogger.ILogger
	Time    time.Time        // 时间
	Level   ilogger.LogLevel // 日志等级
	Message string           // 消息
	Data    ilogger.Fields   // 属性
	Buffer  *bytes.Buffer    //
	Caller  *runtime.Frame
	Context context.Context
	err     string
}

func NewEntry(logger ilogger.ILogger) ilogger.IEntry {
	return &LoggerEntry{
		Logger: logger,
		Data:   make(ilogger.Fields, 1),
	}
}

func (entry *LoggerEntry) GetLevel() ilogger.LogLevel {
	return entry.Level
}

func (entry *LoggerEntry) copy() *LoggerEntry {
	return &LoggerEntry{Logger: entry.Logger, Time: entry.Time, Context: entry.Context, Data: entry.Data, err: entry.err}
}

func (entry *LoggerEntry) Clear() {
	entry.Data = ilogger.EmptyField
}

func (entry *LoggerEntry) Log(level ilogger.LogLevel, args ...any) {
	entry.log(level, fmt.Sprint(args...))
}

func (entry *LoggerEntry) LogF(level ilogger.LogLevel, format string, args ...any) {
	entry.Log(level, fmt.Sprintf(format, args...))
}

// Write 写数据
func (entry *LoggerEntry) Write() {
	entry.Logger.GetLock().Lock()
	defer entry.Logger.GetLock().UnLock()
	serialized, err := entry.Logger.GetFormatter().Format(entry)
	if err != nil {
		_, _ = fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
		return
	}
	// 判断是否需要分割日志
	if entry.Logger.Size()+int64(len(serialized)) > entry.Logger.Max() {
		if err = entry.Logger.Rotate(); err != nil {
			_, _ = fmt.Fprintf(os.Stderr, "Failed to rotate reader, %v\n", err)
			return
		}
	}
	// 写入
	var s int
	if s, err = entry.Logger.GetOutput().Write(serialized); err != nil {
		_, _ = fmt.Fprintf(os.Stderr, "Failed to write to logger, %v\n", err)
	}
	entry.Logger.AppendSize(int64(s))
}

// WithField 添加
func (entry *LoggerEntry) WithField(key string, value any) {
	entry.Data[key] = value
}

// ToMap 转 map
func (entry *LoggerEntry) ToMap() map[string]any {
	m := make(map[string]any)
	m[ilogger.FieldKeyPrefix] = entry.Logger.GetPrefix()
	m[ilogger.FieldKeyTime] = entry.Time
	m[ilogger.FieldKeyLevel] = entry.Level
	m[ilogger.FieldKeyMsg] = entry.Message
	m[ilogger.FieldKeyError] = entry.err
	if entry.Logger.IsCaller() && entry.Caller != nil {
		funcVal := entry.Caller.Function
		fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
		if funcVal != "" {
			m[ilogger.FieldKeyFunc] = funcVal
		}
		if fileVal != "" {
			m[ilogger.FieldKeyFile] = fileVal
		}
	}
	return m
}

// getBufPool 获取缓冲池
func (entry *LoggerEntry) getBufPool() ilogger.IBufferPool {
	return entry.Logger.GetBufPool()
}

func (entry *LoggerEntry) Hooks() {
	var (
		err  error
		hook ilogger.IHook
	)
	entry.Logger.GetLock().Lock()
	hook = entry.Logger.GetHooks()
	entry.Logger.GetLock().UnLock()
	if err = hook.Execute(entry); err != nil {
		_, _ = fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
	}
}

func (entry *LoggerEntry) log(level ilogger.LogLevel, msg string) {
	var (
		pool           ilogger.IBufferPool
		buf            *bytes.Buffer
		newEntry       *LoggerEntry
		isCaller       bool
		getPackageName = func(f string) string {
			for {
				lastPeriod := strings.LastIndex(f, ".")
				lastSlash := strings.LastIndex(f, "/")
				if lastPeriod > lastSlash {
					f = f[:lastPeriod]
				} else {
					break
				}
			}

			return f
		}
		getCaller = func() *runtime.Frame {
			// cache this package's fully-qualified name
			callerInitOnce.Do(func() {
				pcs := make([]uintptr, maximumCallerDepth)
				_ = runtime.Callers(0, pcs)

				// dynamic get the package name and the minimum caller depth
				for i := 0; i < maximumCallerDepth; i++ {
					funcName := runtime.FuncForPC(pcs[i]).Name()
					if strings.Contains(funcName, "blogger") {
						logPackage = getPackageName(funcName)
						break
					}
				}

				minimumCallerDepth = knownLogFrames
			})

			// Restrict the lookback frames to avoid runaway lookups
			pcs := make([]uintptr, maximumCallerDepth)
			depth := runtime.Callers(minimumCallerDepth, pcs)
			frames := runtime.CallersFrames(pcs[:depth])

			for f, again := frames.Next(); again; f, again = frames.Next() {
				pkg := getPackageName(f.Function)

				// If the caller isn't part of this package, we're done
				if pkg != logPackage {
					return &f //nolint:scopelint
				}
			}
			// if we got here, we failed to find the caller's context
			return nil
		}
		clearBuf = func() {
			newEntry.Buffer = nil
			buf.Reset()
			pool.Put(buf)
		}
	)
	newEntry = entry.copy()
	if newEntry.Time.IsZero() {
		newEntry.Time = time.Now()
	}
	newEntry.Level = level
	newEntry.Message = msg

	// 调用信息
	newEntry.Logger.GetLock().Lock()
	isCaller = newEntry.Logger.IsCaller()
	pool = newEntry.getBufPool()
	newEntry.Logger.GetLock().UnLock()
	if isCaller {
		newEntry.Caller = getCaller()
	}

	// 执行钩子函数
	newEntry.Hooks()
	buf = pool.Get()
	defer clearBuf()
	buf.Reset()
	newEntry.Buffer = buf
	newEntry.Write()
	newEntry.Buffer = nil
}
